/*
 * RequestAppointmentHandler.java
 * 
 * @version 1.0, March 10, 2011
 * CS 2340 Team 10: Cyberdyne Systems
 */
package edu.gatech.oad.cyberdoc.domain.action;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import edu.gatech.oad.cyberdoc.domain.Appointment;

import edu.gatech.oad.cyberdoc.domain.Patient;
import edu.gatech.oad.cyberdoc.domain.User;
import edu.gatech.oad.cyberdoc.domain.filter.TypeFilter;

/**
 * Handles user actions to view available appointments.
 * 
 * @author John Girata
 * @version 1.0, March 6, 2011
 */
public class RequestAppointmentHandler extends ActionHandler {
	/**
	 * The logger for this class.
	 */
	private static final Logger LOG;

	/**
	 * The action code this ActionHandler corresponds to.
	 */
	public static final String ACTION_CODE;

	/**
	 * The possible time slots, as a number of milliseconds after midnight.
	 */
	private final long[] slots;

	static {
		ACTION_CODE = "request_appt";
		LOG = Logger.getLogger("edu.gatech.oad.cyberdoc.domain."
				+ "RequestAppointmentHandler");
	}

	/**
	 * Registers this ActionHandler with the Patient class to start receiving
	 * actions.
	 * 
	 * This class requires an array storing all of the possible time slots that
	 * an appointment may be registered.
	 * 
	 * @param slots
	 *            The possible time slots, as a number of milliseconds after
	 *            midnight
	 */
	public RequestAppointmentHandler(long[] slots) {
		this.slots = slots.clone();

		Patient.addActionHandler(ACTION_CODE, this);
	}

	/**
	 * Reverses everything done by the constructor.
	 */
	@Override
	public void destroy() {
		Patient.removeActionHandler(this);
	}

	/**
	 * Retrieves the list of available appointments for a given day.
	 * 
	 * This method expects the following fields:
	 * 
	 * datetime: The day to retrieve appointments for. This can be any time
	 * during the day, and should be represented as the number of milliseconds
	 * since the Unix epoch.
	 * 
	 * This method will return the following fields:
	 * 
	 * next: The next frame to show the user
	 * 
	 * datetime: The datetime sent with this request
	 * 
	 * times: The times, in milliseconds since midnight, of the available
	 * appointments
	 * 
	 * @param source
	 *            The user that called this method
	 * @param command
	 *            The command encoding the action
	 * @return The result of performing this action
	 * @throws ActionHandleException
	 *             If a required field does not exist in the command, or if the
	 *             usernames given for the patient and doctor are invalid
	 */
	@Override
	public JSONObject handle(User source, JSONObject command)
			throws ActionHandleException {
		final JSONObject response = new JSONObject();

		try {
			/*
			 * This will work basically like the merge method of merge sort:
			 * 
			 * 1. Sort the current appointments and the possible slots.
			 * 
			 * 2. Compare the first slot against the first appointment. If they
			 * conflict, remove that available slot and go on to the next slot
			 * and next appointment. If they do not conflict, go on to the next
			 * slot if the slot is earlier than the appointment, and go on to
			 * the next appointment if the slot is later than the appointment.
			 * 
			 * 3. Repeat step 2 until we reach the end of the slots or the
			 * appointments.
			 */
			final long millis = Long.parseLong(command.getString("datetime"));
			final List<Appointment> appts = Appointment.getByDate(new Date(
					millis));

			final Calendar midnight = Calendar.getInstance();
			midnight.setTime(new Date(millis));

			midnight.set(Calendar.HOUR_OF_DAY, 0);
			midnight.set(Calendar.MINUTE, 0);
			midnight.set(Calendar.SECOND, 0);
			midnight.set(Calendar.MILLISECOND, 0);

			Collections.sort(appts, new Comparator<Appointment>() {

				@Override
				public int compare(Appointment o1, Appointment o2) {
					final Date d1 = o1.getDate();
					final Date d2 = o2.getDate();

					return d1.compareTo(d2);
				}
			});

			final List<Long> possible = new ArrayList<Long>();

			for (long i : slots) {
				possible.add(midnight.getTimeInMillis() + i);
			}

			Collections.sort(possible);

			int slot = 0;
			int appt = 0;

			while (slot < possible.size() && appt < appts.size()) {
				Date apptDate = appts.get(appt).getDate();
				Date possDate = new Date(possible.get(slot));

				int comparison = apptDate.compareTo(possDate);

				if (comparison == 0) {
					possible.remove(slot);
				} else if (comparison < 0) {
					appt++;
				} else {
					slot++;
				}
			}

			final List<User> doctors = User.filterUsers(new TypeFilter(Class
					.forName("edu.gatech.oad.cyberdoc.domain.Doctor")));

			final JSONArray timesArray = new JSONArray(possible);
			final JSONArray doctorsArray = new JSONArray();
			
			for(User doctor : doctors) {
				doctorsArray.put(doctor.toJSONObject());
			}
			
			response.put("next", "view_appts");
			response.put("datetime", command.getString("datetime"));
			response.put("times", timesArray);
			response.put("doctors", doctorsArray);
		} catch (JSONException e) {
			LOG.log(Level.SEVERE, "Unexpected exception while writing to "
					+ "response object.", e);
			throw new ActionHandleException(e);
		} catch (NumberFormatException e) {
			LOG.log(Level.SEVERE, "Datetime string was in a bad format", e);
			throw new ActionHandleException(e);
		} catch (ClassNotFoundException e) {
			LOG.log(Level.SEVERE, "Doctor class does not exist", e);
			throw new ActionHandleException(e);
		}

		return response;
	}

	/**
	 * Returns a string representation of this class.
	 * 
	 * @return A string representation of this class.
	 */
	@Override
	public String toString() {
		return "RequestAppointmentHandler (action=\"" + ACTION_CODE + "\")";
	}
}
